require 'io/console'

module Rbrainfuck
  class Interpreter

    # [todo] - Allow to pass an array of options
    # [todo] - Allow to specify in and out streams
    def initialize(size)
      @cells = []
      @stack = []
      @pointer = 0

      size.times { @cells << 0 }
    end

    def self.allowed_chars
      self.private_instance_methods(false).map { |name| name[1..-1].to_i.chr }
    end

    def load(source)
      @source = source.split(//)
    end

    def run
      @position = 0

      while @position < @source.count
        begin
          interpret_char(@source[@position]) if Interpreter.allowed_chars.include?(@source[@position])
          @position += 1
        rescue Exception => e
          # [todo] - Use a logger
          puts "#{@position}:#{@source[@position]} (#{@pointer}:#{@cells[@pointer]}) (#{@nesting_level}) => #{e.message}"
          exit
        end
      end

    end

    private

    def interpret_char(char)
      self.send("_#{char.ord}")
    end

    def find_matching_bracket
      i = @position + 1
      c = 0

      while i < @source.count
        char = @source[i]

        c += 1 if char == '['
        c -= 1 if char == ']'

        return i if c == -1

        i += 1
      end
    end

    # >
    def _62
      @pointer += 1
    end

    # <
    def _60
      @pointer -= 1
    end

    # +
    def _43
      @cells[@pointer] += 1
    end

    # -
    def _45
      @cells[@pointer] -= 1
    end

    # .
    def _46
      print @cells[@pointer].chr
    end

    # ,
    def _44
      @cells[@pointer] = STDIN.getch.ord
    end

    # [
    def _91
      if @cells[@pointer] == 0
        @position = find_matching_bracket
      else
        @stack.push(@position)
      end
    end

    # ]
    def _93
      if @cells[@pointer] == 0
        @stack.pop
      else
        @position = @stack.last
      end
    end

  end
end
